<?php

namespace SLN0002\DAO;

use Home\DAO\DBUtilDAO;
use Home\DAO\PSIBaseExDAO;
use Home\Service\PinyinService;

/**
 * 会计科目 DAO
 *
 * @author PSI
 * @copyright 2015 - present
 * @license GPL v3
 */
class SubjectDAO extends PSIBaseExDAO
{

  /**
   * 根据科目类型获得其图标的css class
   */
  private function subjectIconCls($category)
  {
    // 1：资产、2：负债、4：所有者权益、5：成本、6：损益
    switch ($category) {
      case 1:
        return "PSI-Subject-1";
      case 2:
        return "PSI-Subject-2";
      case 4:
        return "PSI-Subject-4";
      case 5:
        return "PSI-Subject-5";
      case 6:
        return "PSI-Subject-6";
      default:
        return "PSI-Subject";
    }
  }

  private function subjectListInternal($parentId, $companyId)
  {
    $db = $this->db;

    $sql = "select id, code, name, category, is_leaf, balance_dir 
            from t_subject
            where parent_id = '%s' and company_id = '%s'
            order by code ";
    $data = $db->query($sql, $parentId, $companyId);
    $result = [];
    foreach ($data as $v) {
      // 递归调用自己
      $children = $this->subjectListInternal($v["id"], $companyId);

      $result[] = [
        "id" => $v["id"],
        "code" => $v["code"],
        "name" => $v["name"],
        "category" => $v["category"],
        "isLeaf" => $v["is_leaf"] == 1 ? "√" : null,
        "children" => $children,
        "leaf" => count($children) == 0,
        "iconCls" => $this->subjectIconCls($v["category"]),
        "balanceDir" => $this->balanceDirCodeToName($v["balance_dir"]),
        "expanded" => true
      ];
    }

    return $result;
  }

  /**
   * 某个公司的科目码列表
   *
   * @param array $params
   * @return array
   */
  public function subjectList($params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];

    // 判断$companyId是否是公司id
    $sql = "select count(*) as cnt
            from t_org where id = '%s' and parent_id is null ";
    $data = $db->query($sql, $companyId);
    $cnt = $data[0]["cnt"];
    if ($cnt == 0) {
      return $this->emptyResult();
    }

    $result = [];

    $sql = "select id, code, name, category, is_leaf, balance_dir 
            from t_subject
            where parent_id is null and company_id = '%s'
            order by code ";
    $data = $db->query($sql, $companyId);
    foreach ($data as $v) {
      $children = $this->subjectListInternal($v["id"], $companyId);

      $result[] = [
        "id" => $v["id"],
        "code" => $v["code"],
        "name" => $v["name"],
        "category" => $v["category"],
        "isLeaf" => $v["is_leaf"] == 1 ? "√" : null,
        "children" => $children,
        "leaf" => count($children) == 0,
        "iconCls" => $this->subjectIconCls($v["category"]),
        "balanceDir" => $this->balanceDirCodeToName($v["balance_dir"]),
        "expanded" => true,
      ];
    }

    return $result;
  }

  private function insertSubjectInternal(
    $code,
    $name,
    $category,
    $companyId,
    $py,
    $dataOrg,
    $tableName,
    $dir
  ) {
    $db = $this->db;

    $sql = "select count(*) as cnt from t_subject where code = '%s' and company_id = '%s' ";
    $data = $db->query($sql, $code, $companyId);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return;
    }

    $id = $this->newId();

    $isLeaf = 0;

    // 本年利润 - 设置为末级科目
    if ($code == "4103") {
      $isLeaf = 1;
    }

    $sql = "insert into t_subject(id, category, code, name, is_leaf, py, data_org, company_id, parent_id,
              gl_table_name, balance_dir)
            values ('%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', null, '%s', %d)";
    $rc = $db->execute($sql, $id, $category, $code, $name, $isLeaf, $py, $dataOrg, $companyId, $tableName, $dir);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    return null;
  }

  /**
   * 创建一级科目的总账数据库表
   */
  public function createGlTableForStandardSubject($companyId)
  {
    $db = $this->db;

    $sql = "select gl_table_name
            from t_subject
            where (company_id = '%s') and (gl_table_name is not null or gl_table_name <> '')
            order by code";
    $data = $db->query($sql, $companyId);

    foreach ($data as $v) {
      $tableName = $v["gl_table_name"];
      $sql = "CREATE TABLE IF NOT EXISTS `{$tableName}` (
                `id` bigint(20) NOT NULL AUTO_INCREMENT,
                `acc_id` varchar(255) NOT NULL,
                `company_id` varchar(255) NOT NULL,
                `acc_user_id` varchar(255) DEFAULT NULL,
                `biz_user_id` varchar(255) DEFAULT NULL,
                `subject_code` varchar(255) DEFAULT NULL,
                `voucher_dt` date DEFAULT NULL,
                `voucher_year` int(11) DEFAULT NULL,
                `voucher_month` int(11) DEFAULT NULL,
                `acc_user_name` varchar(255) DEFAULT NULL,
                `biz_user_name` varchar(255) DEFAULT NULL,
                `acc_db` decimal(19,2) DEFAULT NULL,
                `acc_cr` decimal(19,2) DEFAULT NULL,
                `acc_balance` decimal(19,2) DEFAULT NULL,
                `acc_balance_dbcr` varchar(255) DEFAULT NULL,
                `voucher_summary` varchar(255) DEFAULT NULL,
                `acc_flag` int(11) DEFAULT 1500,
                PRIMARY KEY (`id`)
              ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1
             ";
      // acc_flag字段：
      // -1 期初建账； 1000 - 月期初；1500 - 月中日常账务记录； 2000 - 月期末；3000 - 年期末； 1001 - 年期初(应该不会使用)
      // acc_flag是给账做大分类的排序，id是流水号，但是是由系统自动生成的
      // 在一个会计期间内，先按acc_flag排序，再按id流水号排序
      // 主要是应付某些情况：账簿记录已经产生了，但是流水号是自增长，想再加一个月末统计记录，就可以插入一条acc_id为2001的，这样就能排到月末记录
      // 当然了，这是一个备用补丁方案，应该是不能经常用到。
      $db->execute($sql);
    }
  }

  /**
   * 国家标准科目表
   *
   * @return array
   */
  private function getStandardSubjectList()
  {
    $result = [];

    $result[] = [
      "code" => "1001",
      "name" => "库存现金",
      "category" => 1,
      "dir" => 1, // 余额方向
    ];
    $result[] = [
      "code" => "1002",
      "name" => "银行存款",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1012",
      "name" => "其他货币资金",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1101",
      "name" => "交易性金融资产",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1121",
      "name" => "应收票据",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1122",
      "name" => "应收账款",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1123",
      "name" => "预付账款",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1131",
      "name" => "应收股利",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1132",
      "name" => "应收利息",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1221",
      "name" => "其他应收款",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1231",
      "name" => "坏账准备",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1401",
      "name" => "材料采购",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1402",
      "name" => "在途物资",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1403",
      "name" => "原材料",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1405",
      "name" => "库存商品",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1406",
      "name" => "发出商品",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1408",
      "name" => "委托加工物资",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1411",
      "name" => "周转材料",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1511",
      "name" => "长期股权投资",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1601",
      "name" => "固定资产",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1602",
      "name" => "累计折旧",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1604",
      "name" => "在建工程",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1605",
      "name" => "工程物资",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1606",
      "name" => "固定资产清理",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1701",
      "name" => "无形资产",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1702",
      "name" => "累计摊销",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1801",
      "name" => "长期待摊费用",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "1901",
      "name" => "待处理财产损溢",
      "category" => 1,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "2001",
      "name" => "短期借款",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2201",
      "name" => "应付票据",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2202",
      "name" => "应付账款",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2203",
      "name" => "预收账款",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2211",
      "name" => "应付职工薪酬",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2221",
      "name" => "应交税费",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2231",
      "name" => "应付利息",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2232",
      "name" => "应付股利",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2241",
      "name" => "其他应付款",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "2501",
      "name" => "长期借款",
      "category" => 2,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "4001",
      "name" => "实收资本",
      "category" => 4,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "4002",
      "name" => "资本公积",
      "category" => 4,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "4101",
      "name" => "盈余公积",
      "category" => 4,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "4103",
      "name" => "本年利润",
      "category" => 4,
      "dir" => 0,
    ];
    $result[] = [
      "code" => "4104",
      "name" => "利润分配",
      "category" => 4,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "5001",
      "name" => "生产成本",
      "category" => 5,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "5101",
      "name" => "制造费用",
      "category" => 5,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "5201",
      "name" => "劳务成本",
      "category" => 5,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6001",
      "name" => "主营业务收入",
      "category" => 6,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "6051",
      "name" => "其他业务收入",
      "category" => 6,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "6111",
      "name" => "投资收益",
      "category" => 6,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "6301",
      "name" => "营业外收入",
      "category" => 6,
      "dir" => 2,
    ];
    $result[] = [
      "code" => "6401",
      "name" => "主营业务成本",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6402",
      "name" => "其他业务成本",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6403",
      "name" => "营业税金及附加",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6601",
      "name" => "销售费用",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6602",
      "name" => "管理费用",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6603",
      "name" => "财务费用",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6701",
      "name" => "资产减值损失",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6711",
      "name" => "营业外支出",
      "category" => 6,
      "dir" => 1,
    ];
    $result[] = [
      "code" => "6801",
      "name" => "所得税费用",
      "category" => 6,
      "dir" => 1,
    ];

    return $result;
  }

  /**
   * 初始国家标准科目
   */
  public function initStandardSubject(&$params, $pinYinService)
  {
    $db = $this->db;

    $dataOrg = $params["dataOrg"];

    $companyId = $params["id"];
    $sql = "select name, data_org 
            from t_org
            where id = '%s' and parent_id is null";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }

    $companyName = $data[0]["name"];
    $orgFlag = $data[0]["data_org"];

    $sql = "select count(*) as cnt from t_subject where company_id = '%s' ";
    $data = $db->query($sql, $companyId);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("国家科目表已经初始化完毕，不能再次初始化");
    }

    $subjectList = $this->getStandardSubjectList();
    foreach ($subjectList as $v) {
      $code = $v["code"];
      $name = $v["name"];
      $category = $v["category"];
      $dir = $v["dir"];

      // 总账的数据库表名
      $tableName = "t_acc_{$orgFlag}_{$code}_gl";

      $rc = $this->insertSubjectInternal(
        $code,
        $name,
        $category,
        $companyId,
        $pinYinService->toPY($name),
        $dataOrg,
        $tableName,
        $dir,
      );
      if ($rc) {
        return $rc;
      }
    }

    // 操作成功
    $params["companyName"] = $companyName;

    return null;
  }

  /**
   * 上级科目字段 - 查询数据
   *
   * @param string $queryKey
   */
  public function queryDataForParentSubject($queryKey, $companyId)
  {
    $db = $this->db;

    // length(code) < 8 : 只查询一级二级科目
    // is_leaf = 0 : 非末级科目
    $sql = "select code, name
            from t_subject
            where (code like '%s' or name like '%s') and (length(code) < 8)
              and (is_leaf = 0) 
              and (company_id = '%s') 
            order by code 
            limit 20 ";
    $queryParams = [];
    $queryParams[] = "{$queryKey}%";
    $queryParams[] = "{$queryKey}%";
    $queryParams[] = $companyId;
    $data = $db->query($sql, $queryParams);

    $result = [];

    foreach ($data as $v) {
      $result[] = [
        "code" => $v["code"],
        "name" => $v["name"]
      ];
    }

    return $result;
  }

  /**
   * 新建科目
   *
   * @param array $params
   */
  public function addSubject(&$params)
  {
    $db = $this->db;

    $dataOrg = $params["dataOrg"];
    if ($this->dataOrgNotExists($dataOrg)) {
      return $this->badParam("dataOrg");
    }

    $companyId = $params["companyId"];
    $sql = "select name from t_org where id = '%s' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];

    $code = $params["code"];
    $name = $params["name"];
    $isLeaf = $params["isLeaf"];
    $balanceDir = intval($params["balanceDir"]);
    if (!in_array($balanceDir, [0, 1, 2])) {
      return $this->badParam("balanceDir");
    }

    $parentCode = $params["parentCode"];
    $sql = "select id, category 
            from t_subject 
            where company_id = '%s' and code = '%s' ";
    $data = $db->query($sql, $companyId, $parentCode);
    if (!$data) {
      return $this->bad("上级科目不存在");
    }
    $parentId = $data[0]["id"];
    $category = $data[0]["category"];

    // 检查科目码是否正确
    if (strlen($parentCode) == 4) {
      // 上级科目是一级科目
      if (strlen($code) != 6) {
        return $this->bad("二级科目码的长度需要是6位");
      }
      if (substr($code, 0, 4) != $parentCode) {
        return $this->bad("二级科目码的前四位必须是一级科目码");
      }
    } else if (strlen($parentCode) == 6) {
      // 上级科目是二级科目
      if (strlen($code) != 8) {
        return $this->bad("三级科目码的长度需要是8位");
      }
      if (substr($code, 0, 6) != $parentCode) {
        return $this->bad("三级科目码的前六位必须是二级科目码");
      }
    } else {
      return $this->bad("上级科目只能是一级科目或者是二级科目");
    }

    // 判断科目码是否已经存在
    $sql = "select count(*) as cnt from t_subject
            where company_id = '%s' and code = '%s' ";
    $data = $db->query($sql, $companyId, $code);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("科目码[{$code}]已经存在");
    }

    $ps = new PinyinService();
    $py = $ps->toPY($name);

    $id = $this->newId();
    $sql = "insert into t_subject(id, category, code, name, is_leaf, py, data_org,
              company_id, parent_id, balance_dir)
            values ('%s', '%s', '%s', '%s', %d, '%s', '%s',
              '%s', '%s', %d)";
    $rc = $db->execute(
      $sql,
      $id,
      $category,
      $code,
      $name,
      $isLeaf,
      $py,
      $dataOrg,
      $companyId,
      $parentId,
      $balanceDir,
    );
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 操作成功
    $params["id"] = $id;
    $params["log"] = "[$companyName] - 新建科目：{$code} - {$name}";
    return null;
  }

  /**
   * 编辑科目
   *
   * @param array $params
   */
  public function updateSubject(&$params)
  {
    $db = $this->db;

    $id = $params["id"];
    $name = $params["name"];
    $isLeaf = $params["isLeaf"];
    $balanceDir = intval($params["balanceDir"]);
    if (!in_array($balanceDir, [0, 1, 2])) {
      return $this->badParam("balanceDir");
    }

    $sql = "select name, code, parent_id, company_id from t_subject where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("要编辑的科目不存在");
    }
    if (!$name) {
      // 当科目是一级科目的时候，前端代码没有把name传回来
      // 导致后面的业务日志中引用name的时候不正确
      // 可以视为对目前代码的一个临时修正
      $name = $data[0]["name"];
    }
    $code = $data[0]["code"];
    $companyId = $data[0]["company_id"];
    $sql = "select name from t_org where id = '%s' ";
    $d = $db->query($sql, $companyId);
    if (!$d) {
      return $this->badParam("companyId");
    }
    $companyName = $d[0]["name"];

    // 检查是否已经初始化了账样
    // 对于已经初始化了账样的科目，其末级科目属性不能更改
    if ($isLeaf != 1) {
      $sql = "select count(*) as cnt 
              from t_acc_fmt 
              where subject_code = '%s' and company_id = '%s' ";
      $data = $db->query($sql, $code, $companyId);
      $cnt = $data[0]["cnt"];
      if ($cnt > 0) {
        return $this->bad("科目[{$code}]之前作为末级科目初始化过账样，这样操作之后就不能将其再修改为非末级科目");
      }
    }

    if ($isLeaf == 1) {
      // 如果该科目还有子科目，则不能把其设置为末级科目
      $sql = "select count(*) as cnt
              from t_subject
              where parent_id = '%s' ";
      $data = $db->query($sql, $id);
      $cnt = $data[0]["cnt"];
      if ($cnt > 0) {
        return $this->bad("科目[{$code}]还有子科目，不能将其修改为末级科目");
      }
    }

    $parentId = $data[0]["parent_id"];
    if (!$parentId) {
      // 当前科目是一级科目，一级科目只能编辑“末级科目”
      $sql = "update t_subject set is_leaf = %d
              where id = '%s' ";
      $rc = $db->execute($sql, $isLeaf, $id);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    } else {
      // 二级或三级科目
      $ps = new PinyinService();
      $py = $ps->toPY($name);
      $sql = "update t_subject
              set name = '%s', py = '%s', is_leaf = %d
              where id = '%s' ";
      $rc = $db->execute($sql, $name, $py, $isLeaf, $id);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 余额方向
    $sql = "update t_subject set balance_dir = %d
            where id = '%s' ";
    $rc = $db->execute($sql, $balanceDir, $id);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 操作成功
    $params["log"] = "[$companyName] - 编辑科目：{$code} - {$name}";
    return null;
  }

  private function isLeafCodeToName($code)
  {
    switch ($code) {
      case 1:
        return "是末级科目";
      case 0:
        return "不是末级科目";
      default:
        return "[未定义]";
    }
  }

  private function balanceDirCodeToName($code)
  {
    switch ($code) {
      case 0:
        return "借贷均可";
      case 1:
        return "借方";
      case 2:
        return "贷方";
      default:
        return "[未定义]";
    }
  }

  /**
   * 某个科目的详情
   *
   * @param array $params
   */
  public function subjectInfo($params)
  {
    $db = $this->db;

    // 科目id
    $id = $params["id"];

    $sql = "select code, name, is_leaf, parent_id, balance_dir 
            from t_subject
            where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->emptyResult();
    }

    $v = $data[0];

    $result = [
      "code" => $v["code"],
      "name" => $v["name"],
      "isLeaf" => $v["is_leaf"],
      "isLeafDisplay" => $this->isLeafCodeToName($v["is_leaf"]),
      "balanceDir" => $v["balance_dir"],
      "balanceDirDisplay" => $this->balanceDirCodeToName($v["balance_dir"]),
      "parentCode" => "[无]"
    ];

    $parentId = $v["parent_id"];
    $sql = "select code, name
            from t_subject
            where id = '%s' ";
    $data = $db->query($sql, $parentId);
    if ($data) {
      $result["parentCode"] = $data[0]["code"] . " - " . $data[0]["name"];
    }

    return $result;
  }

  /**
   * 删除科目
   *
   * @param array $params
   */
  public function deleteSubject(&$params)
  {
    $db = $this->db;

    // 科目id
    $id = $params["id"];

    $sql = "select code, parent_id, company_id from t_subject where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("要删除的科目不存在");
    }
    $companyId = $data[0]["company_id"];
    if ($this->companyIdNotExists($companyId)) {
      return $this->bad("当前科目的companyId字段值异常");
    }

    $code = $data[0]["code"];
    $parentId = $data[0]["parent_id"];
    if (!$parentId) {
      return $this->bad("不能删除一级科目");
    }

    // 检查科目是否有下级科目
    $sql = "select count(*) as cnt from t_subject where parent_id = '%s' ";
    $data = $db->query($sql, $id);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("科目[{$code}]还有下级科目，不能删除");
    }

    // 判断科目是否在账样中使用
    $sql = "select count(*) as cnt 
            from t_acc_fmt
            where company_id = '%s' and subject_code = '%s' ";
    $data = $db->query($sql, $companyId, $code);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("科目[{$code}]已经在账样中使用，不能删除");
    }

    $sql = "delete from t_subject where id = '%s' ";
    $rc = $db->execute($sql, $id);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 操作成功
    $params["code"] = $code;
    return null;
  }

  private function insertFmtCols(
    $fmtId,
    $dbFieldName,
    $dbFieldType,
    $dbFieldLength,
    $dbFieldDecimal,
    $showOrder,
    $caption,
    $colWidth
  ) {
    $db = $this->db;

    $sql = "insert into t_acc_fmt_cols (id, fmt_id, db_field_name, db_field_type,
              db_field_length, db_field_decimal, show_order, caption, sys_col, 
              col_width)
            values ('%s', '%s', '%s', '%s',
              %d, %d, %d, '%s', 1,
              %d)";
    $rc = $db->execute(
      $sql,
      $this->newId(),
      $fmtId,
      $dbFieldName,
      $dbFieldType,
      $dbFieldLength,
      $dbFieldDecimal,
      $showOrder,
      $caption,
      $colWidth
    );
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    return null;
  }

  private function getStandardFmtCols()
  {
    return [
      [
        "name" => "subject_code",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 1,
        "caption" => "科目",
        "colWidth" => 100,
      ],
      [
        "name" => "voucher_dt",
        "type" => "date",
        "length" => 0,
        "decimal" => 0,
        "showOrder" => 2,
        "caption" => "凭证日期",
        "colWidth" => 100,
      ],
      [
        "name" => "voucher_year",
        "type" => "int",
        "length" => 11,
        "decimal" => 0,
        "showOrder" => 3,
        "caption" => "凭证年度",
        "colWidth" => 70,
      ],
      [
        "name" => "voucher_month",
        "type" => "int",
        "length" => 11,
        "decimal" => 0,
        "showOrder" => 4,
        "caption" => "凭证月份",
        "colWidth" => 70,
      ],
      [
        "name" => "voucher_word",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 5,
        "caption" => "凭证字",
        "colWidth" => 70,
      ],
      [
        "name" => "voucher_number",
        "type" => "int",
        "length" => 11,
        "decimal" => 0,
        "showOrder" => 6,
        "caption" => "凭证号",
        "colWidth" => 70,
      ],
      [
        "name" => "je_number",
        "type" => "int",
        "length" => 0,
        "decimal" => 0,
        "showOrder" => 7,
        "caption" => "分录号",
        "colWidth" => 70,
      ],
      [
        "name" => "acc_user_name",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 9000,
        "caption" => "会计经办",
        "colWidth" => 70,
      ],
      [
        "name" => "acc_user_id",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => -1000,
        "caption" => "会计经办id",
        "colWidth" => 70,
      ],
      [
        "name" => "biz_user_name",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 9001,
        "caption" => "业务责任人",
        "colWidth" => 70,
      ],
      [
        "name" => "biz_user_id",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => -1000,
        "caption" => "业务责任人id",
        "colWidth" => 100,
      ],
      [
        "name" => "acc_db",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 10,
        "caption" => "借方金额",
        "colWidth" => 100,
      ],
      [
        "name" => "acc_cr",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 11,
        "caption" => "贷方金额",
        "colWidth" => 100,
      ],
      [
        "name" => "acc_balance",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 12,
        "caption" => "余额金额",
        "colWidth" => 100,
      ],
      [
        "name" => "acc_balance_dbcr",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 13,
        "caption" => "余额方向",
        "colWidth" => 70,
      ],
      [
        "name" => "voucher_summary",
        "type" => "varchar",
        "length" => 255,
        "decimal" => 0,
        "showOrder" => 14,
        "caption" => "摘要",
        "colWidth" => 300,
      ],
    ];
  }

  /**
   * 初始化科目的标准账样
   *
   * @param array $params
   */
  public function initFmt(&$params)
  {
    $db = $this->db;

    $dataOrg = $params["dataOrg"];
    if ($this->dataOrgNotExists($dataOrg)) {
      return $this->badParam("dataOrg");
    }

    // id:科目id
    $id = $params["id"];
    $companyId = $params["companyId"];

    $sql = "select code, is_leaf from t_subject where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("科目不存在");
    }
    $subjectCode = $data[0]["code"];
    $isLeaf = $data[0]["is_leaf"] == 1;
    if (!$isLeaf) {
      return $this->bad("科目[{$subjectCode}]不是末级科目，不能设置账样");
    }

    $sql = "select count(*) as cnt from t_acc_fmt 
            where company_id = '%s' and subject_code = '%s' ";
    $data = $db->query($sql, $companyId, $subjectCode);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("科目[{$subjectCode}]已经完成了标准账样的初始化，不能再次初始化");
    }

    $accNumber = str_pad($subjectCode, 8, "0", STR_PAD_RIGHT);

    // tableName的规则：t_acc_{该组织机构的数据域}_{账簿码}
    // 因为同一个科目的账簿码是一样的，所以还要加上组织机构的数据域来区分不同的数据库表(数据库名需要全局唯一)
    $sql = "select data_org from t_org where id = '%s' ";
    $data = $db->query($sql, $companyId);
    $t = $data[0]["data_org"];
    $tableName = "t_acc_{$t}_{$accNumber}";
    $voucherTableName = "t_voucher_detail_{$t}_{$accNumber}";

    $id = $this->newId();

    $sql = "insert into t_acc_fmt (id, acc_number, subject_code, memo,
              date_created, data_org, company_id, in_use, db_table_name_prefix,
              voucher_db_table_name)
            values ('%s', '%s', '%s', '',
              now(), '%s', '%s', 1, '%s',
              '%s')";
    $rc = $db->execute($sql, $id, $accNumber, $subjectCode, $dataOrg, $companyId, $tableName, $voucherTableName);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 标准列
    $fmtId = $id;
    $cols = $this->getStandardFmtCols();
    foreach ($cols as $v) {
      $rc = $this->insertFmtCols(
        $fmtId,
        $v["name"],
        $v["type"],
        $v["length"],
        $v["decimal"],
        $v["showOrder"],
        $v["caption"],
        $v["colWidth"]
      );
      if ($rc) {
        return $rc;
      }
    }

    // 标记科目已经完成初始化
    // id:科目id
    $id = $params["id"];
    $sql = "update t_subject
            set inited = 1
            where id = '%s' ";
    $rc = $db->execute($sql, $id);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }
    // 如果有上级科目，把上级科目也标记为已经完成初始化
    // 从严格的算法来说，应该用递归，因为有多级科目
    // 但是因为考虑到子账簿的存在，对于固定级别的科目，两级是够用了；如果不够用，以后遇到了再修改
    $sql = "select parent_id
            from t_subject
            where id = '%s' and parent_id is not null";
    $data = $db->query($sql, $id);
    if ($data) {
      $parentId = $data[0]["parent_id"];
      $sql = "update t_subject
              set inited = 1
              where id = '%s' ";
      $rc = $db->execute($sql, $parentId);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 生成日志内容
    $sql = "select name from t_org where id = '%s' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];
    $log = "[{$companyName}] - 初始化科目[{$subjectCode}]的标准账样";
    $params["log"] = $log;

    // 操作成功
    return null;
  }

  /**
   * 创建账样的实际数据库中的物理表
   */
  public function createTableForInitFmt(&$params)
  {
    $db = $this->db;

    // id:科目id
    $id = $params["id"];
    $companyId = $params["companyId"];

    $sql = "select code, is_leaf from t_subject where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("科目不存在");
    }
    $subjectCode = $data[0]["code"];
    $isLeaf = $data[0]["is_leaf"] == 1;
    if (!$isLeaf) {
      return $this->bad("科目[{$subjectCode}]不是末级科目，不能设置账样");
    }

    // 检查FMT是否已经存在
    // FMT是账样元数据，不存在，自然就无法创建实际的数据库表结构
    $sql = "select id, db_table_name_prefix, voucher_db_table_name
            from t_acc_fmt
            where subject_code = '%s' and company_id = '%s' ";
    $data = $db->query($sql, $subjectCode, $companyId);
    if (!$data) {
      return $this->bad("账样元数据不存在，无法创建数据库表结构");
    }

    $fmtId = $data[0]["id"];
    $tableName = $data[0]["db_table_name_prefix"];
    $voucherTableName = $data[0]["voucher_db_table_name"];

    // 检查数据库中表是否已经存在了
    if ((new DBUtilDAO($db))->tableExists($tableName . "_detail")) {
      return $this->bad("表{$tableName}_detail已经存在，无法再次创建");
    }
    if ((new DBUtilDAO($db))->tableExists($voucherTableName)) {
      return $this->bad("表{$voucherTableName}已经存在，无法再次创建");
    }

    // db_field_name以x_开头的是自动生成凭证使用的系统自动
    // 这里先把它排除掉，是为了让后续创建其字段的代码顺利运行
    $sql = "select db_field_name, db_field_type, db_field_length, db_field_decimal 
            from t_acc_fmt_cols
            where fmt_id = '%s' and left(db_field_name, 2) != 'x_' 
            order by show_order";
    $data = $db->query($sql, $fmtId);
    if (!$data) {
      return $this->bad("账样列的元数据不存在，无法创建数据库表结构");
    }

    $sqlForDetail = "CREATE TABLE IF NOT EXISTS `{$tableName}_detail` (\n";
    $sqlForDetail .= "  `id` bigint(20) NOT NULL AUTO_INCREMENT,\n";
    // acc_id: 冗余字段，因id是自动生成的，所以再加一个acc_id编程生成id，这也是PSI一贯的设计风格
    $sqlForDetail .= "  `acc_id` varchar(255) NOT NULL,\n";
    $sqlForDetail .= "  `company_id` varchar(255) NOT NULL,\n";
    // sub_acc_level - 子账簿层级，明细账簿存在子账簿的时候，通过sub_acc_level区分是哪个子账簿
    // sub_acc_level可以取的值：-1, 1, 2, 3，具体参见t_acc_fmt_cols中关于sub_acc_level的说明
    $sqlForDetail .= "  `sub_acc_level` int(11) NOT NULL DEFAULT -1,\n";

    // acc_flag字段：
    // -1 期初建账； 1000 - 月期初；1500 - 月中日常账务记录； 2000 - 月期末；3000 - 年期末； 1001 - 年期初(应该不会使用)
    $sqlForDetail .= "  `acc_flag` int(11) DEFAULT 1500,\n";

    foreach ($data as $v) {
      $fieldName = $v["db_field_name"];
      $fieldType = $v["db_field_type"];
      $fieldLength = $v["db_field_length"];
      $fieldDecimal = $v["db_field_decimal"];

      $type = $fieldType;

      if ($fieldType == "varchar") {
        $type .= "({$fieldLength})";
      } else if ($fieldType == "decimal") {
        $type .= "(19, {$fieldDecimal})";
      } else if ($fieldType == "int") {
        $type .= "(11)";
      }

      $sqlForDetail .= "  `{$fieldName}` {$type} ";
      $sqlForDetail .= ",\n";
    }
    $sqlForDetail .= "  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;\n";

    // 明细账
    $rc = $db->execute($sqlForDetail);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 创建凭证分录的附加表
    $sql = "CREATE TABLE IF NOT EXISTS `{$voucherTableName}` (
              `id` varchar(255) NOT NULL,
              `voucher_id` varchar(255) NOT NULL,
              PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            ";
    $rc = $db->execute($sql);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 操作成功
    return null;
  }

  /**
   * 某个科目的账样属性
   *
   * @param array $params
   */
  public function fmtPropList($params)
  {
    $db = $this->db;
    $result = [];

    // id: 科目id
    $id = $params["id"];
    $companyId = $params["companyId"];

    $sql = "select f.acc_number, f.in_use, f.db_table_name_prefix, f.date_created
            from t_subject s, t_acc_fmt f
            where s.id = '%s' and  s.code = f.subject_code and f.company_id = '%s' ";

    $data = $db->query($sql, $id, $companyId);
    if ($data) {
      $v = $data[0];
      $result[] = [
        "propName" => "账簿码",
        "propValue" => $v["acc_number"]
      ];

      $result[] = [
        "propName" => "状态",
        "propValue" => $v["in_use"] == 1 ? "启用" : "停用"
      ];

      $result[] = [
        "propName" => "表名前缀",
        "propValue" => $v["db_table_name_prefix"]
      ];

      $result[] = [
        "propName" => "初始化时间",
        "propValue" => $v["date_created"]
      ];
    }

    return $result;
  }

  /**
   * 某个科目的账样字段列表
   *
   * @param array $params
   */
  public function fmtColsList($params)
  {
    $db = $this->db;
    $result = [];

    // id: 科目id
    $id = $params["id"];
    $companyId = $params["companyId"];

    $sql = "select c.id, c.show_order, c.caption, c.db_field_name, c.db_field_type,
              c.sys_col,
              c.db_field_length, c.db_field_decimal, c.voucher_input, c.voucher_input_show_order,
              c.code_table_name, c.voucher_input_xtype, c.voucher_input_colspan, c.voucher_input_width,
              c.sub_acc_level, c.col_width, c.col_category, c.col_cpm
            from t_subject s, t_acc_fmt f, t_acc_fmt_cols c
            where s.id = '%s' and s.code = f.subject_code and f.company_id = '%s'
              and f.id = c.fmt_id
            order by c.show_order";
    $data = $db->query($sql, $id, $companyId);
    foreach ($data as $v) {
      $result[] = [
        "id" => $v["id"],
        "showOrder" => $v["show_order"],
        "caption" => $v["caption"],
        "fieldName" => $v["db_field_name"],
        "fieldType" => $v["db_field_type"],
        "fieldLength" => $v["db_field_length"] == 0 ? null : $v["db_field_length"],
        "fieldDecimal" => $v["db_field_decimal"] == 0 ? null : $v["db_field_decimal"],
        "voucherInput" => "{$v["voucher_input"]} - " . $this->voucherInputCodeToName($v["voucher_input"]),
        "voucherInputShowOrder" => $v["voucher_input_show_order"],
        "sysCol" => $v["sys_col"] == 1 ? "▲" : "账样扩展项",
        "sysColRaw" => $v["sys_col"],
        "codeTableName" => $v["code_table_name"],
        "voucherInputXtype" => $v["voucher_input_xtype"],
        "voucherInputColspan" => $v["sys_col"] == 1 ? "" : $v["voucher_input_colspan"],
        "voucherInputWidth" => $v["sys_col"] == 1 ? "" : $v["voucher_input_width"],
        "subAccLevel" => $v["sub_acc_level"] > 0 ? $v["sub_acc_level"] : "",
        "colWidth" => $v["col_width"],
        "fmtCategory" => $this->fmtCategoryCodeToName($v["col_category"]),
        "fmtCPM" => $this->fmtCPMCodeToName($v["col_cpm"]),
      ];
    }

    return $result;
  }

  private function voucherInputCodeToName($code)
  {
    $db = $this->db;

    $sql = "select name from t_sysdict_sln0002_fmt_voucher_input where code_int = %d ";
    $data = $db->query($sql, $code);
    if ($data) {
      return $data[0]["name"];
    } else {
      return "[数据未正确初始化]";
    }
  }

  private function voucherInputXtypeCodeToName($code)
  {
    $db = $this->db;

    $sql = "select name from t_sysdict_sln0002_fmt_voucher_input_xtype where code = '%s' ";
    $data = $db->query($sql, $code);
    if ($data) {
      return $data[0]["name"];
    } else {
      return "[数据未正确初始化]";
    }
  }

  private function fmtCategoryCodeToName($code)
  {
    $db = $this->db;

    if ($code == "0") {
      // 为了界面上显示简洁，比较这个属性只对三栏账有效
      return "";
    }

    $sql = "select name from t_sysdict_sln0002_fmt_category where code = '%s' ";
    $data = $db->query($sql, $code);
    if ($data) {
      return $code . "-" . $data[0]["name"];
    } else {
      return "[数据未正确初始化]";
    }
  }

  private function fmtCPMCodeToName($code)
  {
    $db = $this->db;

    if ($code == "0") {
      // 为了界面显示简洁
      return "";
    }

    $sql = "select name from t_sysdict_sln0002_fmt_cpm where code = '%s' ";
    $data = $db->query($sql, $code);
    if ($data) {
      return $code . "-" . $data[0]["name"];
    } else {
      return "[数据未正确初始化]";
    }
  }

  /**
   * 盘点字符串中是否都是小写字母或是下划线
   *
   * @param string $s
   * @return boolean
   */
  private function strIsAllLetters($s)
  {
    for ($i = 0; $i < strlen($s); $i++) {
      $c = ord($s[$i]);
      if ($c == ord('_')) {
        continue;
      }

      if (ord('a') > $c || $c > ord('z')) {
        return false;
      }
    }

    return true;
  }

  /**
   * 新增账样字段
   *
   * @param array $params
   */
  public function addFmtCol(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $subjectCode = $params["subjectCode"];
    $fieldCaption = $params["fieldCaption"];
    $fieldName = strtolower($params["fieldName"]);
    $fieldType = $params["fieldType"];
    $fmtCategory = $params["fmtCategory"];

    $voucherInputShowOrder = $params["voucherInputShowOrder"];
    $voucherInput = intval($params["voucherInput"]);
    $voucherInputXtype = $params["voucherInputXtype"];
    $voucherInputColspan = intval($params["voucherInputColspan"]);
    $voucherInputWidth = intval($params["voucherInputWidth"]);
    $codeTableName = $params["codeTableName"];

    if ($voucherInputColspan < 1) {
      $voucherInputColspan = 1;
    }
    if ($voucherInputColspan > 4) {
      $voucherInputColspan = 4;
    }
    if ($voucherInputWidth < 10) {
      $voucherInputWidth = 10;
    }
    if ($voucherInputWidth > 1000) {
      $voucherInputWidth = 1000;
    }

    $subAccLevel = intval($params["subAccLevel"]);
    if ($subAccLevel < 0) {
      $subAccLevel = -1;
    }
    if ($subAccLevel > 3) {
      return $this->bad("子账簿层级最大到3级");
    }

    $colWidth = intval($params["colWidth"]);
    if ($colWidth < 10) {
      $colWidth = 10;
    }
    if ($colWidth > 1000) {
      $colWidth = 1000;
    }

    $fmtCPM = $params["fmtCPM"];

    // 检查companyId
    $sql = "select name from t_org where id = '%s' and parent_id is null";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];

    if (!in_array($voucherInput, [1, 2])) {
      return $this->badParam($voucherInput);
    }

    // 检查$voucherInputXtype
    $sql = "select count(*) as cnt from 	
              t_sysdict_sln0002_fmt_voucher_input_xtype
            where code = '%s' ";
    $data = $db->query($sql, $voucherInputXtype);
    $cnt = $data[0]["cnt"];
    if ($cnt != 1) {
      return $this->badParam($voucherInputXtype);
    }

    if ($voucherInput == 2) {
      // 码表录入

      if (!$codeTableName) {
        return $this->bad("没有选择码表录入的引用表名");
      }

      // 检查码表和字段是否存在
      $util = new DBUtilDAO($db);
      if (!$util->tableExists($codeTableName)) {
        return $this->bad("表[{$codeTableName}]不存在");
      }
    } else {
      $codeTableName = "";
    }

    // 检查账样
    $sql = "select id, db_table_name_prefix as cnt from t_acc_fmt
            where company_id = '%s' and subject_code = '%s' ";
    $data = $db->query($sql, $companyId, $subjectCode);
    if (!$data) {
      return $this->bad("科目[$subjectCode]的标准账样还没有初始化");
    }
    $dbTableName = $data[0]["db_table_name_prefix"];
    $fmtId = $data[0]["id"];

    // 检查账样是否已经启用
    $util = new DBUtilDAO($db);
    if ($util->tableExists($dbTableName)) {
      return $this->bad("科目[{$subjectCode}]的账样已经启用，不能再新增账样字段");
    }

    // 检查字段名是否合格
    if (strlen($fieldName) == 0) {
      return $this->bad("没有输入数据库字段名");
    }
    if (!$this->strIsAllLetters($fieldName)) {
      return $this->bad("数据库字段名需要是小写字母");
    }
    $sql = "select count(*) as cnt from t_acc_fmt_cols 
            where fmt_id = '%s' and db_field_name = '%s' ";
    $data = $db->query($sql, $fmtId, $fieldName);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("科目[{$subjectCode}]的账样中已经存在字段[{$fieldName}]");
    }

    $type = "varchar";
    $length = 255;
    $dec = 0;
    switch ($fieldType) {
      case 1:
        $type = "varchar";
        $length = 255;
        $dec = 0;
        break;
      case 2:
        $type = "date";
        $length = 0;
        $dec = 0;
        break;
      case 3:
        $type = "decimal";
        $length = 19;
        $dec = 2;
        break;
      case 4:
        $type = "int";
        $length = 11;
        $dec = 0;
        break;
      case 5:
        $type = "decimal";
        $length = 19;
        $dec = 8;
        break;
      default:
        return $this->bad("字段类型不正确");
    }

    $sql = "select max(show_order) as max_show_order from t_acc_fmt_cols
            where fmt_id = '%s' and show_order > 0 ";
    $data = $db->query($sql, $fmtId);
    $cnt = $data[0]["max_show_order"];
    $showOrder = $cnt + 1;

    $id = $this->newId();
    $sql = "insert into t_acc_fmt_cols (id, fmt_id, caption, db_field_name,
              db_field_type, db_field_length, db_field_decimal, show_order, sys_col,
              voucher_input_show_order, voucher_input, code_table_name, voucher_input_xtype,
              voucher_input_colspan, voucher_input_width, sub_acc_level, col_width,
              col_category, col_cpm)
            values ('%s', '%s', '%s', '%s',
              '%s', %d, %d, %d, 0,
              %d, %d, '%s', '%s',
              %d, %d, %d, %d,
              %d, %d)";
    $rc = $db->execute(
      $sql,
      $id,
      $fmtId,
      $fieldCaption,
      $fieldName,
      $type,
      $length,
      $dec,
      $showOrder,
      $voucherInputShowOrder,
      $voucherInput,
      $codeTableName,
      $voucherInputXtype,
      $voucherInputColspan,
      $voucherInputWidth,
      $subAccLevel,
      $colWidth,
      $fmtCategory,
      $fmtCPM,
    );
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 业务日志
    $log = "[{$companyName}] - 新建科目[{$subjectCode}]的账样字段[{$fieldCaption}]";
    $params["log"] = $log;

    // 操作成功
    $params["id"] = $id;
    return null;
  }

  /**
   * 在数据库中创建账样列对应的字段
   */
  public function createTableColForFMT(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $subjectCode = $params["subjectCode"];
    $fieldName = strtolower($params["fieldName"]);
    $fieldType = $params["fieldType"];
    $voucherInput = intval($params["voucherInput"]);

    $sql = "select db_table_name_prefix, voucher_db_table_name
            from t_acc_fmt
            where subject_code = '%s' and company_id = '%s' ";
    $data = $db->query($sql, $subjectCode, $companyId);
    if (!$data) {
      return $this->bad("账样不存在");
    }

    $tableName = $data[0]["db_table_name_prefix"];
    $tableNameDetail = $tableName . "_detail";
    $tableNameVoucher = $data[0]["voucher_db_table_name"];
    // 在数据库表中创建字段
    $sqlForDetail = "ALTER TABLE {$tableNameDetail} ADD {$fieldName} ";
    $sqlForVoucher = "ALTER TABLE {$tableNameVoucher} ADD {$fieldName} ";

    $type = "";
    switch ($fieldType) {
      case 1:
        $type = " varchar(255) ";
        break;
      case 2:
        $type = " datetime ";
        break;
      case 3:
        $type = " decimal(19, 2) ";
        break;
      case 4:
        $type = " int(11) ";
        break;
      case 5:
        $type = " decimal(19, 8) ";
        break;
      default:
        return $this->bad("字段类型还不支持");
    }

    $sqlForDetail .= " {$type} DEFAULT NULL;";
    $sqlForVoucher .= " {$type} DEFAULT NULL;";

    // 明细账表
    $rc = $db->execute($sqlForDetail);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 凭证分录附加表
    $rc = $db->execute($sqlForVoucher);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    if ($voucherInput == 2) {
      // 码表录入

      // 当是码表录入的时候，$fieldName字段里面保存码表的code，
      // 再创建一个字段来保存码表的name，字段名的规则是：在$fieldName后面加上后缀"_name"
      $fieldCodeName = $fieldName . "_name";
      $sqlForDetail = "ALTER TABLE {$tableNameDetail} ADD {$fieldCodeName} varchar(255) DEFAULT NULL";
      $sqlForVoucher = "ALTER TABLE {$tableNameVoucher} ADD {$fieldCodeName} varchar(255) DEFAULT NULL";

      // 明细账表
      $rc = $db->execute($sqlForDetail);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }

      // 凭证分录附加表
      $rc = $db->execute($sqlForVoucher);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }

      // 还创建一个字段来保存码表的id，字段名的规则是：在$fieldName后面加上后缀"_id"
      $fieldCodeName = $fieldName . "_id";
      $sqlForDetail = "ALTER TABLE {$tableNameDetail} ADD {$fieldCodeName} varchar(255) DEFAULT NULL";
      $sqlForVoucher = "ALTER TABLE {$tableNameVoucher} ADD {$fieldCodeName} varchar(255) DEFAULT NULL";

      // 明细账表
      $rc = $db->execute($sqlForDetail);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }

      // 凭证分录附加表
      $rc = $db->execute($sqlForVoucher);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 操作成功
    return null;
  }

  /**
   * 编辑账样字段
   *
   * @param array $params
   */
  public function updateFmtCol(&$params)
  {
    $db = $this->db;

    $id = $params["id"];
    $companyId = $params["companyId"];
    $subjectCode = $params["subjectCode"];
    $fieldCaption = $params["fieldCaption"];
    $fmtCategory = $params["fmtCategory"];

    $voucherInputShowOrder = $params["voucherInputShowOrder"];
    $voucherInput = intval($params["voucherInput"]);
    $voucherInputXtype = $params["voucherInputXtype"];
    $codeTableName = $params["codeTableName"];

    $voucherInputColspan = intval($params["voucherInputColspan"]);
    $voucherInputWidth = intval($params["voucherInputWidth"]);
    if ($voucherInputColspan < 1) {
      $voucherInputColspan = 1;
    }
    if ($voucherInputColspan > 4) {
      $voucherInputColspan = 4;
    }
    if ($voucherInputWidth < 10) {
      $voucherInputWidth = 10;
    }
    if ($voucherInputWidth > 1000) {
      $voucherInputWidth = 1000;
    }

    $subAccLevel = intval($params["subAccLevel"]);
    if ($subAccLevel < 0) {
      $subAccLevel = -1;
    }
    if ($subAccLevel > 3) {
      return $this->bad("子账簿层级最大到3级");
    }

    $colWidth = intval($params["colWidth"]);
    if ($colWidth < 10) {
      $colWidth = 10;
    }
    if ($colWidth > 1000) {
      $colWidth = 1000;
    }

    $fmtCPM = $params["fmtCPM"];

    // 检查companyId
    $sql = "select name from t_org where id = '%s' and parent_id is null";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];


    if (!in_array($voucherInput, [1, 2, 3, 4, 5])) {
      return $this->badParam($voucherInput);
    }

    // 检查$voucherInputXtype
    $sql = "select count(*) as cnt from 	
              t_sysdict_sln0002_fmt_voucher_input_xtype
            where code = '%s' ";
    $data = $db->query($sql, $voucherInputXtype);
    $cnt = $data[0]["cnt"];
    if ($cnt != 1) {
      return $this->badParam($voucherInputXtype);
    }

    if ($voucherInput == 2) {
      // 码表录入

      // 检查码表和字段是否存在
      $util = new DBUtilDAO($db);
      if (!$util->tableExists($codeTableName)) {
        return $this->bad("表[{$codeTableName}]不存在");
      }
    } else {
      $codeTableName = "";
    }

    // 检查账样
    $sql = "select fmt_id, sys_col from t_acc_fmt_cols where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("要编辑的账样字段不存在");
    }
    $fmtId = $data[0]["fmt_id"];
    $sysCol = $data[0]["sys_col"] == 1;
    if ($sysCol) {
      return $this->bad("标准账样字段不能编辑");
    }

    $sql = "select db_table_name_prefix from t_acc_fmt where id = '%s' ";
    $data = $db->query($sql, $fmtId);
    if (!$data) {
      return $this->bad("账样不存在");
    }

    // 账样已经创建了数据库表，这个时候就不能修改账样的字段类型了

    $sql = "update t_acc_fmt_cols
            set caption = '%s', voucher_input_show_order = %d,
              voucher_input = %d, code_table_name = '%s',
              voucher_input_xtype = '%s', voucher_input_colspan = %d,
              voucher_input_width = %d, sub_acc_level = %d, col_width = %d,
              col_category= %d, col_cpm = %d 
            where id = '%s' ";
    $rc = $db->execute(
      $sql,
      $fieldCaption,
      $voucherInputShowOrder,
      $voucherInput,
      $codeTableName,
      $voucherInputXtype,
      $voucherInputColspan,
      $voucherInputWidth,
      $subAccLevel,
      $colWidth,
      $fmtCategory,
      $fmtCPM,
      $id,
    );
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 业务日志
    $log = "[{$companyName}] - 编辑科目[{$subjectCode}]的账样字段[{$fieldCaption}]";
    $params["log"] = $log;

    // 操作成功
    return null;
  }

  private function fieldTypeNameToCode($name, $dec)
  {
    switch ($name) {
      case "varchar":
        return 1;
      case "date":
        return 2;
      case "decimal": {
          if ($dec == 2) {
            return 3;
          }
          if ($dec == 8) {
            return 5;
          } else {
            return 0;
          }
        }
      case "int":
        return 4;
      default:
        return 0;
    }
  }

  private function fieldTypeCodeToName($code)
  {
    $db = $this->db;

    $sql = "select name from t_sysdict_sln0002_fmt_field_type where code = '%s' ";
    $data = $db->query($sql, $code);
    if ($data) {
      return $data[0]["name"];
    } else {
      return "[未定义]";
    }
  }

  /**
   * 获得某个账样字段的详情
   */
  public function fmtColInfo($params)
  {
    $db = $this->db;
    $id = $params["id"];

    $result = [];

    $sql = "select caption, db_field_name, sys_col,
              db_field_type, db_field_decimal, voucher_input_show_order, voucher_input, code_table_name, voucher_input_xtype,
              voucher_input_colspan, voucher_input_width, sub_acc_level, col_width,
              col_category, col_cpm 
            from t_acc_fmt_cols 
            where id = '%s' ";
    $data = $db->query($sql, $id);
    if ($data) {
      $v = $data[0];

      $result["caption"] = $v["caption"];
      $result["fieldName"] = $v["db_field_name"];
      $result["sysCol"] = $v["sys_col"];
      $fieldType = $this->fieldTypeNameToCode($v["db_field_type"], $v["db_field_decimal"]);
      $result["fieldType"] = $fieldType;
      $result["fieldTypeName"] = $this->fieldTypeCodeToName($fieldType);
      $result["voucherInputShowOrder"] = $v["voucher_input_show_order"];
      $result["voucherInput"] = $v["voucher_input"];
      $result["voucherInputName"] = $this->voucherInputCodeToName($v["voucher_input"]);
      $result["codeTableName"] = $v["code_table_name"];
      $result["voucherInputXtype"] = $v["voucher_input_xtype"];
      $result["voucherInputColspan"] = $v["voucher_input_colspan"];
      $result["voucherInputWidth"] = $v["voucher_input_width"];
      $result["voucherInputXtypeName"] = $this->voucherInputXtypeCodeToName($v["voucher_input_xtype"]);
      $result["subAccLevel"] = $v["sub_acc_level"];
      $result["colWidth"] = $v["col_width"];
      $result["fmtCategory"] = $v["col_category"];
      $result["fmtCategoryName"] = $this->fmtCategoryCodeToName($v["col_category"]);
      $result["fmtCPM"] = $v["col_cpm"];
      $result["fmtCPMName"] = $this->fmtCPMCodeToName($v["col_cpm"]);
    }

    return $result;
  }

  /**
   * 删除某个账样字段
   */
  public function deleteFmtCol(&$params)
  {
    $db = $this->db;

    $id = $params["id"];

    $sql = "select fmt_id, caption, sys_col 
            from t_acc_fmt_cols
            where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("要删除的账样字段不存在");
    }
    $v = $data[0];
    $fmtId = $v["fmt_id"];
    $caption = $v["caption"];
    $sysCol = $v["sys_col"];
    if ($sysCol == 1) {
      return $this->bad("账样字段[{$caption}]是标准账样字段，不能删除");
    }

    $sql = "select subject_code, acc_number, db_table_name_prefix 
            from t_acc_fmt 
            where id = '%s' ";
    $data = $db->query($sql, $fmtId);
    if (!$data) {
      return $this->bad("账样不存在");
    }
    $v = $data[0];
    $subjectCode = $v["subject_code"];
    $accNumber = $v["acc_number"];

    $sql = "delete from t_acc_fmt_cols where id = '%s' ";
    $rc = $db->execute($sql, $id);

    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 操作成功
    $params["caption"] = $caption;
    $params["subjectCode"] = $subjectCode;
    $params["accNumber"] = $accNumber;
    return null;
  }

  /**
   * 某个账样所有字段 - 设置字段显示次序用
   */
  public function fmtGridColsList($params)
  {
    $db = $this->db;

    // id - 科目的id
    $id = $params["id"];
    $sql = "select c.id, c.caption, c.col_width
            from t_subject s, t_acc_fmt f, t_acc_fmt_cols c
            where s.id = '%s' 
              and s.company_id = f.company_id and s.code = f.subject_code
              and f.id = c.fmt_id and c.show_order > 0
              and c.col_category != 1
            order by c.show_order";
    $result = [];
    $data = $db->query($sql, $id);
    foreach ($data as $v) {
      $result[] = [
        "id" => $v["id"],
        "caption" => $v["caption"],
        "colWidth" => $v["col_width"],
      ];
    }

    return $result;
  }

  /**
   * 编辑账样字段的显示次序
   */
  public function editFmtColShowOrder(&$params)
  {
    $db = $this->db;

    // id:科目id
    $id = $params["id"];

    // 账样字段id，以逗号分隔形成的List
    $idList = $params["idList"];
    // 列宽度，以逗号分隔
    $widthList = $params["widthList"];

    $idArray = explode(",", $idList);
    $widthArray = explode(",", $widthList);
    if (count($idArray) != count($widthArray)) {
      // bug
      return $this->badParam("widthList");
    }

    $sql = "select company_id, code from t_subject where id = '%s' ";
    $data = $db->query($sql, $id);
    if (!$data) {
      return $this->bad("科目不存在");
    }
    $v = $data[0];
    $subjectCode = $v["code"];

    foreach ($idArray as $i => $colId) {
      $showOrder = $i + 1;

      $width = $widthArray[$i];

      $sql = "update t_acc_fmt_cols
              set show_order = %d, col_width = %d
              where id = '%s' ";
      $rc = $db->execute($sql, $showOrder, $width, $colId);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 操作成功
    $params["subjectCode"] = $subjectCode;
    return null;
  }

  /**
   * 选择值来源的引用列 - 查询表
   */
  public function queryTablesForColRef($params)
  {
    $db = $this->db;

    $result = [];
    $searchKey = $params["searchKey"];

    $sql = "select name, table_name from t_code_table_md";
    $queryParams = [];
    if ($searchKey) {
      $sql .= " where table_name like '%s' ";
      $queryParams[] = "%{$searchKey}%";
    }
    $sql .= " order by table_name";
    $data = $db->query($sql, $queryParams);
    foreach ($data as $v) {
      $result[] = [
        "name" => $v["table_name"],
        "caption" => $v["name"],
      ];
    }

    return $result;
  }

  /**
   * 科目自定义字段， 查询数据
   *
   * @param array $params
   * @return array
   */
  public function queryDataForSubjectField($params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $queryKey = $params["queryKey"];

    $queryParams = [];

    $result = [];

    $sql = "select id, code, name
            from t_subject
            where (company_id = '%s') and (inited = 1) and (is_leaf = 1)";
    $queryParams[] = $companyId;
    if ($queryKey) {
      $sql .= " and ((code like '%s') or (py like '%s') or (name like '%s'))";
      $queryParams[] = "%{$queryKey}%";
      $queryParams[] = "%{$queryKey}%";
      $queryParams[] = "%{$queryKey}%";
    }
    $sql .= " order by code ";

    $data = $db->query($sql, $queryParams);
    foreach ($data as $v) {
      $result[] = [
        "id" => $v["id"],
        "code" => $v["code"],
        "name" => $v["name"],
      ];
    }

    return $result;
  }

  /**
   * 一键生成数量金额三栏账样列
   */
  public function genCPMFields(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $subjectCode = $params["subjectCode"];

    // 检查参数
    $sql = "select name 
            from t_org
            where id = '%s' and parent_id is null";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];

    $sql = "select id
            from t_acc_fmt
            where company_id = '%s' and subject_code = '%s' and in_use = 1";
    $data = $db->query($sql, $companyId, $subjectCode);
    if (!$data) {
      return $this->badParam("subjectCode");
    }
    $fmtId = $data[0]["id"];

    // 数量金额三栏账的列有
    // x_v_cnt 数量，这个字段仅供凭证录入数据用，账簿里面不存储
    // x_db_cnt 借方数量、 x_db_price 借方单价
    // x_cr_cnt 借方数量、 x_cr_price 借方单价
    // x_balance_cnt 余额数量、 x_balance_price 余额单价

    // 生成元数据
    $colsMd = [
      [
        "name" => "x_v_cnt",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 8,
        "showOrder" => 100,
        "caption" => "数量",
        "colWidth" => 100,
        "colCPM" => 1,
        "colCategory" => 1,
        "voucherInputXtype" => "numberfield",
      ],
      [
        "name" => "x_db_cnt",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 8,
        "showOrder" => 101,
        "caption" => "借方数量",
        "colWidth" => 100,
        "colCPM" => 10,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
      [
        "name" => "x_db_price",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 102,
        "caption" => "借方单价",
        "colWidth" => 100,
        "colCPM" => 20,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
      [
        "name" => "x_cr_cnt",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 8,
        "showOrder" => 103,
        "caption" => "贷方数量",
        "colWidth" => 100,
        "colCPM" => 30,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
      [
        "name" => "x_cr_price",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 104,
        "caption" => "贷方单价",
        "colWidth" => 100,
        "colCPM" => 40,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
      [
        "name" => "x_balance_cnt",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 8,
        "showOrder" => 105,
        "caption" => "余额数量",
        "colWidth" => 100,
        "colCPM" => 50,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
      [
        "name" => "x_balance_price",
        "type" => "decimal",
        "length" => 19,
        "decimal" => 2,
        "showOrder" => 106,
        "caption" => "余额单价",
        "colWidth" => 100,
        "colCPM" => 60,
        "colCategory" => 2,
        "voucherInputXtype" => "", // 账簿字段，所以不需要编辑器
      ],
    ];
    foreach ($colsMd as $v) {
      // 检查是否已经存在了
      $dbFieldName = $v["name"];
      $sql = "select count(*) as cnt
              from t_acc_fmt_cols
              where fmt_id = '%s' and db_field_name = '%s' ";
      $data = $db->query($sql, $fmtId, $dbFieldName);
      $cnt = $data[0]["cnt"];
      if ($cnt > 0) {
        return $this->bad("列[{$dbFieldName}已经存在");
      }

      $sql = "insert into t_acc_fmt_cols (id, fmt_id, db_field_name, db_field_type,
              db_field_length, db_field_decimal, show_order, caption, sys_col, 
              col_width, col_cpm, col_category, voucher_input_xtype)
            values ('%s', '%s', '%s', '%s',
              %d, %d, %d, '%s', 0,
              %d, %d, %d, '%s')";
      $rc = $db->execute(
        $sql,
        $this->newId(),
        $fmtId,
        $v["name"],
        $v["type"],
        $v["length"],
        $v["decimal"],
        $v["showOrder"],
        $v["caption"],
        $v["colWidth"],
        $v["colCPM"],
        $v["colCategory"],
        $v["voucherInputXtype"],
      );
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 因为数据库事务的原因，实际创建表字段的代码在本事务结束后，由别的方法执行

    // 业务日志
    $params["log"] = "[{$companyName}] - 为科目[{$subjectCode}]一键生成数量金额三栏账样列";

    // 操作成功
    return null;
  }

  /**
   * 创建数据库字段 - 一键生成数量金额三栏账样列
   */
  public function createDbFieldsForCPM($params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $subjectCode = $params["subjectCode"];

    $sql = "select db_table_name_prefix, voucher_db_table_name
            from t_acc_fmt
            where company_id = '%s' and subject_code = '%s' and in_use = 1";
    $data = $db->query($sql, $companyId, $subjectCode);
    if (!$data) {
      return $this->badParam("subjectCode");
    }
    $accTableName = $data[0]["db_table_name_prefix"];
    $voucherTableName = $data[0]["voucher_db_table_name"];

    // 凭证
    $tableName = $voucherTableName;
    $sql = "alter table {$tableName} add x_v_cnt decimal(19,8)";
    $rc = $db->execute($sql);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 明细账
    $tableName = $accTableName . "_detail";
    $sql = "alter table {$tableName} add x_db_cnt decimal(19,8) default null;
            alter table {$tableName} add x_db_price decimal(19,2) default null;
            alter table {$tableName} add x_cr_cnt decimal(19,8) default null;
            alter table {$tableName} add x_cr_price decimal(19,2) default null;
            alter table {$tableName} add x_balance_cnt decimal(19,8) default null;
            alter table {$tableName} add x_balance_price decimal(19,2) default null;
            ";
    $rc = $db->execute($sql);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }
  }

  /**
   * 初始化标准进销存账样
   */
  public function initStandardFmtForSLN0001(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    $sql = "select name, fi_vat_type, fi_acc_standards 
            from t_org 
            where id = '%s' and parent_id is null";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      return $this->badParam("companyId");
    }
    $companyName = $data[0]["name"];
    $vatType = $data[0]["fi_vat_type"];
    $accStandards = $data[0]["fi_acc_standards"];
    if (!in_array($vatType, [1, 2])) {
      $info = "{$companyName}还没有设置增值税纳税类型<br/>请在模块<span style='color:blue'>用户管理</span>中使用功能<span style='color:blue'>编辑组织机构</span>设置正确的纳税类型";
      return $this->bad($info);
    }

    if (!in_array($accStandards, [1, 2])) {
      $info = "{$companyName}还没有设置实施会计准则<br/>请在模块<span style='color:blue'>用户管理</span>中使用功能<span style='color:blue'>编辑组织机构</span>设置正确的会计准则";
      return $this->bad($info);
    }

    if ($accStandards != 2) {
      $info = "目前只支持 <span style='color:blue'>小企业会计准则</span>";
      return $this->bad($info);
    }

    // 检查是否初始化了国家标准会计科目
    $sql = "select count(*) as cnt from t_subject where company_id = '%s' ";
    $data = $db->query($sql, $companyId);
    $cnt = $data[0]["cnt"];
    if ($cnt > 0) {
      return $this->bad("{$companyName}已经初始化过国家标准会计科目了，这时候因为情况未知，系统不能自动初始化标准进销存账样");
    }

    // 初始化国家标准会计科目
    $initParams = [
      "dataOrg" => $dataOrg,
      "id" => $companyId,
    ];
    $pinYinService = $params["pinyinService"];
    $rc = $this->initStandardSubject($initParams, $pinYinService);
    if ($rc) {
      return $rc;
    }

    // -------------------------------------------
    // 1001 - 库存现金
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1001($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id100101"] = $initParams["id100101"];

    // -------------------------------------------
    // 1002 - 银行存款
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1002($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id100201"] = $initParams["id100201"];

    // -------------------------------------------
    // 1122 - 应收账款
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1122($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id112201"] = $initParams["id112201"];
    $params["id112202"] = $initParams["id112202"];

    // -------------------------------------------
    // 1123 - 预付账款
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1123($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id112301"] = $initParams["id112301"];
    $params["id112302"] = $initParams["id112302"];

    // -------------------------------------------
    // 1402 - 在途物资
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1402($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id140201"] = $initParams["id140201"];

    // -------------------------------------------
    // 1405 - 库存商品
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_1405($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id140501"] = $initParams["id140501"];

    // -------------------------------------------
    // 2202 - 应付账款
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_2202($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id220201"] = $initParams["id220201"];
    $params["id220202"] = $initParams["id220202"];

    // -------------------------------------------
    // 2221 - 应交税费
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = null;
    if ($vatType == 1) {
      $rc = $this->initStandardFmt_SLN0001_2221_general($initParams);
      $params["id22210101"] = $initParams["id22210101"];
      $params["id22210102"] = $initParams["id22210102"];
      $params["id222102"] = $initParams["id222102"];
    } else {
      $rc = $this->initStandardFmt_SLN0001_2221_small($initParams);
      $params["id222101"] = $initParams["id222101"];
    }
    if ($rc) {
      return $rc;
    }

    // -------------------------------------------
    // 4103 - 本年利润
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_4103($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id4103"] = $initParams["id4103"];

    // -------------------------------------------
    // 6001 - 主营业务收入
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_6001($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id600101"] = $initParams["id600101"];

    // -------------------------------------------
    // 6401 - 主营业务成本
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_6401($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id640101"] = $initParams["id640101"];

    // -------------------------------------------
    // 6601 - 销售费用
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_6601($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id660101"] = $initParams["id660101"];
    $params["id660102"] = $initParams["id660102"];

    // -------------------------------------------
    // 6602 - 管理费用
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_6602($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id660201"] = $initParams["id660201"];
    $params["id660202"] = $initParams["id660202"];

    // -------------------------------------------
    // 6603 - 财务费用
    // -------------------------------------------
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initStandardFmt_SLN0001_6603($initParams);
    if ($rc) {
      return $rc;
    }
    $params["id660301"] = $initParams["id660301"];
    $params["id660302"] = $initParams["id660302"];

    // ----------------------------------------------
    // 标记初始化标准进销存完毕
    $sql = "update t_org
            set fi_inited_by_sln0001 = 1
            where id = '%s' ";
    $rc = $db->execute($sql, $companyId);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    $params["vatType"] = $vatType;

    // 业务日志
    $log = "{$companyName} - 初始化标准进销存账样";
    $params["log"] = $log;

    // 操作成功
    return null;
  }

  /**
   * 标准进销存 - 初始化科目1001 - 库存现金
   */
  private function initStandardFmt_SLN0001_1001(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 库存现金增加一个子科目100101 - 库存现金 - 本币
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "100101",
      "name" => "库存现金 - 本币",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1001",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id100101 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id100101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id100101"] = $id100101;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目1002 - 银行存款
   */
  private function initStandardFmt_SLN0001_1002(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 银行存款增加一个子科目100201 - 银行存款 - 本币
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "100201",
      "name" => "银行存款 - 本币",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1002",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id100201 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id100201,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加：银行账号，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "100201",
      "fieldCaption" => "银行账号",
      "fieldName" => "x_bank",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_bank",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id100201"] = $id100201;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目1122 - 应收账款
   */
  private function initStandardFmt_SLN0001_1122(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 应收账款增加两个子科目
    // 112201 应收账款 - 客户
    // 112201 应收账款 - 客户（暂估）
    // -----------------------------------------
    // 关于应收账款为什么会有暂估，参考这些文章
    // https://zhuanlan.zhihu.com/p/519494749
    // https://www.zhihu.com/question/446597381/answer/1752820529
    // ------------------------------------------

    // 应收账款增加子科目112201 - 应收账款 - 客户
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "112201",
      "name" => "应收账款 - 客户",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1122",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id112201 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id112201,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加：客户，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "112201",
      "fieldCaption" => "客户",
      "fieldName" => "x_customer",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_customer",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 应收账款增加子科目112202 - 应收账款 - 客户（暂估）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "112202",
      "name" => "应收账款 - 客户（暂估）",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1122",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id112202 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id112202,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加：客户，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "112202",
      "fieldCaption" => "客户",
      "fieldName" => "x_customer",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_customer",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id112201"] = $id112201;
    $params["id112202"] = $id112202;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目1123 - 预付账款
   */
  private function initStandardFmt_SLN0001_1123(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 预付账款增加两个子科目
    // 112301 - 预付账款 - 供应商
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "112301",
      "name" => "预付账款 - 供应商",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1123",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id112301 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id112301,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加供应商账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "112301",
      "fieldCaption" => "供应商",
      "fieldName" => "x_supplier",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_supplier",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id112301"] = $id112301;

    return null;
  }

  /**
   * 标准进销存 - 初始化科目1402 - 在途物资
   */
  private function initStandardFmt_SLN0001_1402(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 1402增加一个子科目： 140201 在途物资 - 物料
    // 增加一个子科目的原则是：尽量不使用一级科目来记账，除了少数科目（例如：本年利润）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "140201",
      "name" => "在途物资 - 物料",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1402",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id140201 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id140201,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加物料账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "140201",
      "fieldCaption" => "物料",
      "fieldName" => "x_goods",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_goods",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加数量金额三栏账样
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "140201",
    ];
    $rc = $this->genCPMFields($initParams);
    if ($rc) {
      return $rc;
    }

    $sql = "select id 
            from t_acc_fmt
            where company_id = '%s' and subject_code = '140201' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目140201没有初始化");
    }
    $fmtId = $data[0]["id"];

    // x_v_cnt字段的凭证录入次序修改为2
    $sql = "update t_acc_fmt_cols
            set voucher_input_show_order = 2
            where fmt_id = '%s' and db_field_name = 'x_v_cnt' ";
    $rc = $db->execute($sql, $fmtId);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 重新排列账样的字段次序
    $colList = [
      "subject_code",
      "voucher_dt",
      "voucher_year",
      "voucher_month",
      "voucher_word",
      "voucher_number",
      "je_number",
      "x_db_cnt",
      "x_db_price",
      "acc_db",
      "x_cr_cnt",
      "x_cr_price",
      "acc_cr",
      "x_balance_cnt",
      "x_balance_price",
      "acc_balance",
      "acc_balance_dbcr",
      "x_goods",
      "voucher_summary",
      "acc_user_name",
      "biz_user_name",
    ];
    $sql = "select id from t_acc_fmt where company_id = '%s' and subject_code = '140201' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目140201账样不存在");
    }
    $fmtId = $data[0]["id"];
    foreach ($colList as $i => $fieldName) {
      $showOrder = $i + 1;
      $sql = "update t_acc_fmt_cols
              set show_order = %d
              where fmt_id = '%s' and db_field_name = '%s' ";
      $rc = $db->execute($sql, $showOrder, $fmtId, $fieldName);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 操作成功
    $params["id140201"] = $id140201;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目1405 - 库存商品
   */
  private function initStandardFmt_SLN0001_1405(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 1405增加一个子科目，140501 库存商品 - 仓库 - 物料
    // 增加一个子科目的原则是：尽量不使用一级科目来记账，除了少数科目（例如：本年利润）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "140501",
      "name" => "库存商品 - 仓库 - 物料",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "1405",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id140501 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id140501,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加仓库账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "140501",
      "fieldCaption" => "仓库",
      "fieldName" => "x_warehouse",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_warehouse",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加物料账样字段，二级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "140501",
      "fieldCaption" => "物料",
      "fieldName" => "x_goods",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 2,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_goods",
      "subAccLevel" => 2, // 二级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加数量金额三栏账样
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "140501",
    ];
    $rc = $this->genCPMFields($initParams);
    if ($rc) {
      return $rc;
    }

    $sql = "select id 
            from t_acc_fmt
            where company_id = '%s' and subject_code = '140501' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目140501没有初始化");
    }
    $fmtId = $data[0]["id"];

    // x_v_cnt字段的凭证录入次序修改为3，前两个依次是：仓库和物料
    $sql = "update t_acc_fmt_cols
            set voucher_input_show_order = 3
            where fmt_id = '%s' and db_field_name = 'x_v_cnt' ";
    $rc = $db->execute($sql, $fmtId);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 重新排列账样的字段次序
    $colList = [
      "subject_code",
      "voucher_dt",
      "voucher_year",
      "voucher_month",
      "voucher_word",
      "voucher_number",
      "je_number",
      "x_db_cnt",
      "x_db_price",
      "acc_db",
      "x_cr_cnt",
      "x_cr_price",
      "acc_cr",
      "x_balance_cnt",
      "x_balance_price",
      "acc_balance",
      "acc_balance_dbcr",
      "x_warehouse",
      "x_goods",
      "voucher_summary",
      "acc_user_name",
      "biz_user_name",
    ];
    $sql = "select id from t_acc_fmt where company_id = '%s' and subject_code = '140501' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目140501账样不存在");
    }
    $fmtId = $data[0]["id"];
    foreach ($colList as $i => $fieldName) {
      $showOrder = $i + 1;
      $sql = "update t_acc_fmt_cols
              set show_order = %d
              where fmt_id = '%s' and db_field_name = '%s' ";
      $rc = $db->execute($sql, $showOrder, $fmtId, $fieldName);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 操作成功
    $params["id140501"] = $id140501;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目2202 - 应付账款
   */
  private function initStandardFmt_SLN0001_2202(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 应付账款增加两个子科目
    // 220201 - 应付账款 - 供应商
    // 220202 - 应付账款 - 供应商（暂估）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "220201",
      "name" => "应付账款 - 供应商",
      "isLeaf" => 1,
      "balanceDir" => 2,
      "parentCode" => "2202",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id220201 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id220201,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加供应商账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "220201",
      "fieldCaption" => "供应商",
      "fieldName" => "x_supplier",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_supplier",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 科目：220202
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "220202",
      "name" => "应付账款 - 供应商（暂估）",
      "isLeaf" => 1,
      "balanceDir" => 2,
      "parentCode" => "2202",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id220202 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id220202,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加供应商账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "220202",
      "fieldCaption" => "供应商",
      "fieldName" => "x_supplier",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_supplier",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id220201"] = $id220201;
    $params["id220202"] = $id220202;

    return null;
  }

  /**
   * 标准进销存 - 初始化科目4103 - 本年利润
   */
  private function initStandardFmt_SLN0001_4103(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 直接按末级科目初始化，不需要自定义账样

    $sql = "select id 
            from t_subject
            where company_id = '%s' and code = '4103' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目4103不存在");
    }
    $id4103 = $data[0]["id"];

    // 把4103修改为末级科目
    $sql = "update t_subject
            set is_leaf = 1
            where id = '%s' ";
    $rc = $db->execute($sql, $id4103);
    if ($rc === false) {
      return $this->sqlError(__METHOD__, __LINE__);
    }

    // 初始化4103标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id4103,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id4103"] = $id4103;
    return null;
  }

  //----------------------------------------
  /**
   * 关于应交税费的说明
   * 小规模纳税人和一般纳税人，在处理增值税上有很大的不同
   * 
   * 一般纳税人的应交税费设置，共三级科目
   * 应交税费
   *   - 应交增值税
   *     - 进项税额                发生额 -> 借方
   *     - 销项税额抵减             发生额 -> 借方
   *     - 已交税金                发生额 -> 借方
   *     - 转出未交增值税           发生额 -> 借方
   *     - 减免税款                发生额 -> 借方
   *     - 出口抵减内销产品应纳税额   发生额 -> 借方
   *     - 销项税额                发生额 -> 贷方
   *     - 进项税额转出             发生额 -> 贷方
   *     - 转出多交增值税           发生额 -> 贷方
   *     - 出口退税                发生额 -> 贷方
   *   - 未交增值税
   *   - 预交增值税
   *   - 简易计税
   *   - 待抵扣进项税额
   *   - 待认证进项税额
   *   - 待转销项税额
   *   - 代扣代交增值税
   *   - 增值税留抵税额
   *   - 转让金融商品应交增值税
   *   - 增值税检查调整
   * 
   * 小规模纳税人的应交税费设置，共二级科目
   * 应交税费
   *   - 应交增值税
   *   - 转入金融商品应交增值税
   *   - 代扣代交增值税
   */
  //----------------------------------------


  /**
   * 标准进销存 - 初始化科目2221 - 应交税费，小规模纳税人使用
   */
  private function initStandardFmt_SLN0001_2221_small(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 应交税费增加一个子科目
    // 222101 - 应交税费 - 应交增值税
    // 转入金融商品应交增值税 和 代扣代交增值税，这两个子科目暂时不默认增加
    // 待以后具体需求的时候再增加

    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "222101",
      "name" => "应交税费 - 应交增值税",
      "isLeaf" => 1,
      "balanceDir" => 2,
      "parentCode" => "2221",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id222101 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id222101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id222101"] = $id222101;

    return null;
  }

  /**
   * 标准进销存 - 初始化科目2221 - 应交税费，一般纳税人使用
   */
  private function initStandardFmt_SLN0001_2221_general(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    //   应交税费
    //     - 应交增值税
    //       - 进项税额
    //       - 销项税额
    //     - 未交增值税

    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "222101",
      "name" => "应交税费 - 应交增值税",
      "isLeaf" => 0, // 不是末级科目
      "balanceDir" => 2,
      "parentCode" => "2221",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id222101 = $initParams["id"];
    // 三级子科目 22210101
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "22210101",
      "name" => "应交税费 - 应交增值税 - 进项税额",
      "isLeaf" => 1, // 是末级科目
      "balanceDir" => 1, // 借方
      "parentCode" => "222101",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id22210101 = $initParams["id"];
    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id22210101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 三级子科目 22210102
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "22210102",
      "name" => "应交税费 - 应交增值税 - 销项税额",
      "isLeaf" => 1, // 是末级科目
      "balanceDir" => 2, // 贷方
      "parentCode" => "222101",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id22210102 = $initParams["id"];
    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id22210102,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "222102",
      "name" => "应交税费 - 未交增值税",
      "isLeaf" => 1, // 是末级科目
      "balanceDir" => 2,
      "parentCode" => "2221",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id222102 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id222102,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id22210101"] = $id22210101;
    $params["id22210102"] = $id22210102;
    $params["id222102"] = $id222102;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目6001 - 主营业务收入
   */
  private function initStandardFmt_SLN0001_6001(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];


    // 6001新增一个子科目：600101 主营业务收入 - 客户
    // 增加一个子科目的原则是：尽量不使用一级科目来记账，除了少数科目（例如：本年利润）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "600101",
      "name" => "主营业务收入 - 客户",
      "isLeaf" => 1,
      "balanceDir" => 2,
      "parentCode" => "6001",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id600101 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id600101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加：客户，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "600101",
      "fieldCaption" => "客户",
      "fieldName" => "x_customer",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_customer",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id600101"] = $id600101;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目6401 - 主营业务成本
   */
  private function initStandardFmt_SLN0001_6401(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 6401增加一个子科目：640101 主营业务成本 - 仓库 - 物料
    // 增加一个子科目的原则是：尽量不使用一级科目来记账，除了少数科目（例如：本年利润）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "640101",
      "name" => "主营业务成本 - 仓库 - 物料",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6401",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id640101 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id640101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加仓库账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "640101",
      "fieldCaption" => "仓库",
      "fieldName" => "x_warehouse",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_warehouse",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加物料账样字段，二级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "640101",
      "fieldCaption" => "物料",
      "fieldName" => "x_goods",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 2,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_goods",
      "subAccLevel" => 2, // 二级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 重新排列账样的字段次序
    $colList = [
      "subject_code",
      "voucher_dt",
      "voucher_year",
      "voucher_month",
      "voucher_word",
      "voucher_number",
      "je_number",
      "acc_db",
      "acc_cr",
      "acc_balance",
      "acc_balance_dbcr",
      "x_warehouse",
      "x_goods",
      "voucher_summary",
      "acc_user_name",
      "biz_user_name",
    ];
    $sql = "select id from t_acc_fmt where company_id = '%s' and subject_code = '640101' ";
    $data = $db->query($sql, $companyId);
    if (!$data) {
      // 莫名的bug
      return $this->bad("科目640101账样不存在");
    }
    $fmtId = $data[0]["id"];
    foreach ($colList as $i => $fieldName) {
      $showOrder = $i + 1;
      $sql = "update t_acc_fmt_cols
              set show_order = %d
              where fmt_id = '%s' and db_field_name = '%s' ";
      $rc = $db->execute($sql, $showOrder, $fmtId, $fieldName);
      if ($rc === false) {
        return $this->sqlError(__METHOD__, __LINE__);
      }
    }

    // 操作成功
    $params["id640101"] = $id640101;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目6601 - 销售费用
   */
  private function initStandardFmt_SLN0001_6601(&$params)
  {
    $db = $this->db;

    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 销售费用增加两个子科目
    // 660101: 销售费用 - 费用项
    // 660102: 销售费用 - 费用项（暂估）

    // 660101 : 销售费用 - 费用项
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660101",
      "name" => "销售费用 - 费用项",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6601",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660101 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660101,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660101",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_sales",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 660102 : 销售费用 - 费用项（暂估）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660102",
      "name" => "销售费用 - 费用项（暂估）",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6601",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660102 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660102,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660102",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_sales",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id660101"] = $id660101;
    $params["id660102"] = $id660102;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目6602 - 管理费用
   */
  private function initStandardFmt_SLN0001_6602(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 6602增加两个子科目
    // 660201：管理费用 - 费用项
    // 660202：管理费用 - 费用项（暂估）

    // 660201：管理费用 - 费用项
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660201",
      "name" => "管理费用 - 费用项",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6602",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660201 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660201,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660201",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_mgt",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 660202：管理费用 - 费用项（暂估）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660202",
      "name" => "管理费用 - 费用项（暂估）",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6602",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660202 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660202,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660202",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_sales",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id660201"] = $id660201;
    $params["id660202"] = $id660202;
    return null;
  }

  /**
   * 标准进销存 - 初始化科目6603 - 财务费用
   */
  private function initStandardFmt_SLN0001_6603(&$params)
  {
    $companyId = $params["companyId"];
    $dataOrg = $params["dataOrg"];

    // 6603增加两个子科目
    // 660301： 财务费用 - 费用项
    // 660302： 财务费用 - 费用项（暂估）

    // 660301： 财务费用 - 费用项
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660301",
      "name" => "财务费用 - 费用项",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6603",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660301 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660301,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项目账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660301",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_fi",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 660302： 财务费用 - 费用项（暂估）
    $initParams = [
      "companyId" => $companyId,
      "dataOrg" => $dataOrg,
      "code" => "660302",
      "name" => "财务费用 - 费用项（暂估）",
      "isLeaf" => 1,
      "balanceDir" => 1,
      "parentCode" => "6603",
    ];
    $rc = $this->addSubject($initParams);
    if ($rc) {
      return $rc;
    }
    $id660302 = $initParams["id"];

    // 初始化标准账样
    $initParams = [
      "companyId" => $companyId,
      "id" => $id660302,
      "dataOrg" => $dataOrg,
    ];
    $rc = $this->initFmt($initParams);
    if ($rc) {
      return $rc;
    }

    // 增加费用项账样字段，一级子账簿
    $initParams = [
      "companyId" => $companyId,
      "subjectCode" => "660302",
      "fieldCaption" => "费用项",
      "fieldName" => "x_expense",
      "fieldType" => 1, // varchar(255)
      "fmtCategory" => 0,
      "voucherInputShowOrder" => 1,
      "voucherInput" => 2, // 码表录入
      "voucherInputXtype" => "psi_codetable_voucherfield",
      "voucherInputColspan" => 2,
      "voucherInputWidth" => 540,
      "codeTableName" => "t_sln0002_ct_expenses_sales",
      "subAccLevel" => 1, // 一级子账簿
      "colWidth" => 200,
      "fmtCPM" => 0,
    ];
    $rc = $this->addFmtCol($initParams);
    if ($rc) {
      return $rc;
    }

    // 操作成功
    $params["id660301"] = $id660301;
    $params["id660302"] = $id660302;
    return null;
  }

  // -----------------------------------------
  // TODO 还需要增加的科目
  // -----------------------------------------
  /**
   * 222111 应交企业所得税
   * 222112 应交个人所得税
   * 222113 应交教育费附加
   * 222114 应交城市维护建设税
   * 222115 应交地方教育费附加
   * 222199 其他税费
   * 
   * 其他应付款
   *   - 社会保险费
   *   - 住房公积金
   * 
   * 应付职工薪酬
   *   - 工资
   *   - 福利费
   *   - 工会经费
   *   - 职工教育经费
   *   - 社会保险费
   *   - 住房公积金
   * 
   * 固定资产
   *   - 固定资产（一级子账簿）
   * 
   * 实收资本
   * 资本公积
   * 
   * 利润分配
   * 
   * 
   */
}
